package com.lzj.spring.servlet;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.lzj.spring.mvcframework.annotation.LzjAutowrited;
import com.lzj.spring.mvcframework.annotation.LzjController;
import com.lzj.spring.mvcframework.annotation.LzjRequestMapping;
import com.lzj.spring.mvcframework.annotation.LzjService;

public class LzjDispatcherServlet extends HttpServlet{
	
	private Map<String, Object> ioc = new HashMap<String, Object>();
	
	private List<String> classNames = new ArrayList<>();
 	
	private Properties contextConfig = new Properties();
	
	private Map<String, Method> handlerMapping = new HashMap<>();

	@Override
	public void init(ServletConfig config) throws ServletException {
		System.out.println("------------LZJ MVC is init -------------");
		// 1、加载配置文件
		doLoadConfig(config.getInitParameter("contextConfigLocation"));
		
		// 2、根据配置文件扫描所有的相关类
		doScanner(contextConfig.getProperty("scanPackage"));
		
		// 3、初始化所有的相关类的实例，并且将其放入到IOC容器之中，也就是Map中
		doInstance();
		
		// 4、 实现自动依赖注入
		doAutowried();
		
		// 5、初始化HandlerMapping
		initHandlerMapping();
	}
	
	private void initHandlerMapping() {
		if(ioc.isEmpty()) {return;}
		for(Map.Entry<String,Object> entry :ioc.entrySet()) {
			Class<?> clazz = entry.getValue().getClass();
			if(!clazz.isAnnotationPresent(LzjController.class)) {continue;}
			String baseUrl ="";
			if(clazz.isAnnotationPresent(LzjRequestMapping.class)) {
				LzjRequestMapping requestMapping = clazz.getAnnotation(LzjRequestMapping.class);
				baseUrl = requestMapping.value();
			}
			Method[] methods = clazz.getMethods();
			for (Method method : methods) {
				if(!method.isAnnotationPresent(LzjRequestMapping.class)) {continue;}
				LzjRequestMapping requestMapping = method.getAnnotation(LzjRequestMapping.class);
				String url = ("/" + baseUrl + requestMapping.value()).replaceAll("/+", "/");
				// url 和 method的映射关系
				handlerMapping.put(url, method);
				System.out.println("Mapped " +url +"," +method);
			}
			
		}
	}

	private void doAutowried() {
		if(ioc.isEmpty()) {return;}
		for(Map.Entry<String, Object> entry : ioc.entrySet()) {
			Field[] fields = entry.getValue().getClass().getDeclaredFields();
			for (Field field : fields) {
				if(!field.isAnnotationPresent(LzjAutowrited.class)) {continue;}
				LzjAutowrited autowrited = field.getAnnotation(LzjAutowrited.class);
				String beanName = autowrited.value();
				if("".equals(beanName)) {
					//beanName = toLowerFirstCase(field.getType().getSimpleName());
					beanName =toLowerFirstCase(field.getType().getSimpleName());
				}
				try {
					//要想访问到私有的，或者受保护的，我们强制授权访问
					field.setAccessible(true);
					//然后将IOC map中，取出beanName值，给加入注解 @Autowrited 的beanName赋值
					field.set(entry.getValue(), ioc.get(beanName));
				} catch (Exception e) {
					e.printStackTrace();
					continue;
				}
				
			}
		}
	}

	private void doInstance() {
		if(classNames.isEmpty()) {return;}
		try {
			for (String className : classNames) {
				Class<?> clazz = Class.forName(className);
				if(clazz.isAnnotationPresent(LzjController.class)){
					Object instance = clazz.newInstance();
					//key默认类名首字母小写
					String beanName = toLowerFirstCase(clazz.getSimpleName());
					ioc.put(beanName, instance);
				}else if(clazz.isAnnotationPresent(LzjService.class)) {
					//1.默认类名首字母小写
					String beanName = toLowerFirstCase(clazz.getSimpleName());
					LzjService service = clazz.getAnnotation(LzjService.class);
					if(!"".equals(service.value())) {
						//2.自定义命名
						beanName = service.value();
					}
					Object instance = clazz.newInstance();
					ioc.put(beanName, instance);
					//3.根据类型注入
					for(Class<?> i : clazz.getInterfaces()) {
						//如果接口名已存在ioc容器中，抛出异常，该名已存在
						if(ioc.containsKey(i.getSimpleName())) {
							throw new Exception("The beanName "+i.getName()+"is exists!!");
						}
						ioc.put(toLowerFirstCase(i.getSimpleName()), instance);
					}
				}else {
					continue;
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	//首字母小写
	private String toLowerFirstCase(String str) {
		char[] chars = str.toCharArray();
		chars[0] +=32;
		return String.valueOf(chars);
	}

	private void doScanner(String scanPackage) {
		URL url = this.getClass().getClassLoader().getResource("/"+scanPackage.replaceAll("\\.", "/"));
		File classPath = new File(url.getFile());
		for(File file : classPath.listFiles()) {
			if(file.isDirectory()) {
				doScanner(scanPackage+"."+file.getName());
			}else {
				//只获取.class文件
				if(!file.getName().endsWith(".class")) {continue;}
				String className =(scanPackage+"."+file.getName().replace(".class", ""));
				classNames.add(className);
			}
		}
		
	}

	private void doLoadConfig(String contextConfigLocation) {
		InputStream is = this.getClass().getClassLoader().getResourceAsStream(contextConfigLocation);
		try {
			//完成配置文件读取
			contextConfig.load(is);
		} catch (IOException e) {
			e.printStackTrace();
		}finally {
			if(null !=is) {
				try {
					is.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	//---------------------------运行时阶段执行的方法--------------------------------------
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		this.doPost(req, resp);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		try {
			doDispatch(req,resp);
		} catch (Exception e) {
			e.printStackTrace();
			resp.getWriter().write("500 Exception Detail :"+ Arrays.toString(e.getStackTrace()));
		}
	}

	private void doDispatch(HttpServletRequest req, HttpServletResponse resp) throws Exception {
		String url = req.getRequestURI();
		String contextPath = req.getContextPath();
		url = url.replaceAll(contextPath, "").replaceAll("/+", "/");
		if(!this.handlerMapping.containsKey(url)) {
			resp.getWriter().write("404 Not Found!!");
			return;
		}
		Method method = this.handlerMapping.get(url);
		Map<String,String[]> param = req.getParameterMap();
		String beanName = toLowerFirstCase(method.getDeclaringClass().getSimpleName());
		method.invoke(ioc.get(beanName), new Object[] {req,resp,param.get("name")[0]} );
	}
	
	//---------------------------运行时阶段执行的方法--------------------------------------
}
